import { Observable } from "@babylonjs/core/Misc/observable";
import { BabylonContainer } from "../base/babylon-container";


/**
 * 动画帧对象
 * @param id 动画帧对象id
 * @param time 时间记录（毫秒）
 * @param now 当前时间（毫秒）
 * @param counter 计数
 * @param info 信息
 * @param func 回调函数
 * @param observable 要指定的事件对象
 */
interface ITimer {
    id?: number | null,
    time?: number,
    now?: number,
    counter?: number,
    info?: any,
    func?: { (t: number): Promise<boolean | void> | boolean | void },
    observable?: Observable<any>,
}

/**
 * 动画帧请求对象
 * @param id 动画帧请求对象id
 * @param callback 回调函数
 */
interface IRequest {
    id: number,
    callback: { (): void }
}


/**
 * 动画帧，使用前应先使用registered方法绑定到Engine实例
 */
class AnimationFrame {

    private static _requests: Array<IRequest> = [];
    private static _observables: Array<Observable<any> | null | undefined> = [];
    private static _id = 0;

    /**
     * 获取动画帧请求数量
     * @returns 动画帧请求的数量
     */
    public static numberOfRequests(): number {
        return this._requests.length;
    }

    /**
     * 动画帧对象是否正在运行中
     * @param id 动画帧对象或其id
     * @returns 是否正在运行
     */
    public static isRunning(id: number | ITimer): boolean {
        const isId = typeof id === 'number';
        if (!isId) {
            if (id.id === null || id.id === undefined) return false;
        }
        for (let i = 0; i < this._requests.length; i++) {
            if (isId ? (this._requests[i].id !== id) : (this._requests[i].id !== id.id)) continue;
            return true
        }
        return false;
    }

    /**
     * 请求动画帧
     * @param func_t_return_break 回调函数，返回值为true时将停止动画帧
     * @param min 最小间隔（毫秒）
     * @param max 最大间隔（毫秒）
     * @param timerObject 动画帧对象
     * @returns 当前动画帧对象
     */
    public static request(func_t_return_break: { (t: number): Promise<boolean | void> | boolean | void }, min: number = 20, max: number = 200, timerObject?: ITimer): ITimer {
        const timer = timerObject || {};
        if (timer.hasOwnProperty('id')) {
            this.cancel(timer);
        }
        const cur = performance.now();
        timer.info = new Float32Array([min, max, cur, cur, cur]);
        timer.func = func_t_return_break;
        let ani = async () => {
            let info = timer.info;
            info[2] = performance.now();
            let d = info[2] - info[3];
            if (d > info[0]) {
                info[3] = d > info[0] * 2 ? info[2] : info[3] + info[0];
                d = info[2] - info[4];
                info[4] = info[2];
                const val = await timer.func!(d > info[1] ? info[1] : d);
                if (val === true) {
                    AnimationFrame.cancelAnimationFrame(timer.id!);
                }
            }
        };
        timer.id = AnimationFrame.requestAnimationFrame(ani, timer.observable);
        return timer;
    }

    /**
     * 停止动画帧
     * @param timer 要停止的动画帧对象或其id
     */
    public static cancel(timer: ITimer | number): void {
        if (typeof timer === 'number') {
            AnimationFrame.cancelAnimationFrame(timer as number);
            return;
        }
        if (!timer.hasOwnProperty('id')) return;
        AnimationFrame.cancelAnimationFrame((timer as ITimer).id!);
        delete (timer as any).id;
        delete (timer as any).counter;
    }

    /**
     * 按帧数进行节流
     * @param frameObject 动画帧对象
     * @param frameInterval 间隔帧数
     * @param fuc 回调函数
     * @param isHead 是否首执行（默认true）
     */
    public static throttleByFrame(frameObject: ITimer, frameInterval: number, fuc: { (): void }, isHead: boolean = true): void {
        if (isHead) {
            if (frameObject.id !== null && frameObject.id !== undefined) {
                return;
            }
            frameObject.counter = 0;
            const ani = () => {
                frameObject.counter!++;
                if (frameObject.counter! > frameInterval) {
                    AnimationFrame.cancelAnimationFrame(frameObject.id!);
                    frameObject.id = null;
                    fuc();
                }
            };
            frameObject.id = AnimationFrame.requestAnimationFrame(ani, frameObject.observable);
        } else {
            if (!frameObject.counter) frameObject.counter = 0;
            if (frameObject.id !== null && frameObject.id !== undefined) {
                AnimationFrame.cancelAnimationFrame(frameObject.id);
                frameObject.id = null;
            }
            const ani = () => {
                frameObject.counter!++;
                if (frameObject.counter! > frameInterval) {
                    AnimationFrame.cancelAnimationFrame(frameObject.id!);
                    frameObject.id = null;
                    frameObject.counter = 0;
                    fuc();
                }
            };
            frameObject.id = AnimationFrame.requestAnimationFrame(ani, frameObject.observable);
        }
    }

    /**
     * 按时间进行节流
     * @param timeObject 动画帧对象 
     * @param timeInterval 间隔时间（毫秒）
     * @param fuc 回掉函数
     * @param isHead 是否首执行（默认true）
     */
    public static throttleByTime(timeObject: ITimer, timeInterval: number, fuc: { (): void }, isHead: boolean = true): void {
        if (isHead) {
            if (timeObject.id !== null && timeObject.id !== undefined) {
                return;
            }
            timeObject.counter = performance.now() + timeInterval;
            const ani = () => {
                if (timeObject.counter! < performance.now()) {
                    AnimationFrame.cancelAnimationFrame(timeObject.id!);
                    timeObject.id = null;
                    fuc();
                }

            };
            timeObject.id = AnimationFrame.requestAnimationFrame(ani, timeObject.observable);
        } else {
            if (!timeObject.counter) timeObject.counter = performance.now() + timeInterval;
            if (timeObject.id !== null && timeObject.id !== undefined) {
                AnimationFrame.cancelAnimationFrame(timeObject.id);
                timeObject.id = null;
            }
            const ani = () => {
                if ((timeObject.counter!) < performance.now()) {
                    AnimationFrame.cancelAnimationFrame(timeObject.id!);
                    timeObject.id = null;
                    timeObject.counter = 0;
                    fuc();
                }
            };
            timeObject.id = AnimationFrame.requestAnimationFrame(ani, timeObject.observable);
        }
    }

    /**
     * 请求动画帧
     * @param callback 回调函数 
     * @param observable 要指定的事件对象 
     * @returns 动画帧id
     */
    public static requestAnimationFrame(callback: { (): void }, observable?: Observable<any>): number {
        if (!BabylonContainer.engine) {
            console.error('missing available engine!');
            return 0;
        }
        this._id++;
        const id = this._id;
        const rq: IRequest = {
            id: id,
            callback: callback,
        };
        this._requests.push(rq);
        this._observables.push(observable);
        if (observable) {
            observable.add(rq.callback);
        } else {
            BabylonContainer.engine.runRenderLoop(rq.callback);
        }
        return id;
    }

    /**
     * 停止动画帧
     * @param id 动画帧对象id
     */
    public static cancelAnimationFrame(id: number): void {
        if (!BabylonContainer.engine) {
            console.error('missing available engine!');
            return;
        }
        let idx = -1;
        for (let i = 0; i < this._requests.length; i++) {
            if (this._requests[i].id !== id) continue;
            idx = i;
            break;
        }
        if (idx < 0) return;
        if (this._observables[idx]) {
            this._observables[idx]!.removeCallback(this._requests[idx].callback);
        } else {
            BabylonContainer.engine.stopRenderLoop(this._requests[idx].callback);
        }
        this._requests.splice(idx, 1);
        this._observables.splice(idx, 1);
    }

}


export { AnimationFrame };
export type { ITimer };